from tqsdk import TqApi, TqAccount, TqSim, TargetPosTask
from tqsdk.ta import BOLL
import os, time, math, sqlite3
from apscheduler.schedulers.background import BackgroundScheduler
import configparser
from myfun import * 


#读取配置文件
cp = configparser.ConfigParser()
cp.read("config.conf",encoding="utf-8")
#读取账户信息
cp_broker_id = cp.get('default', 'broker_id')
cp_account_id = cp.get('default', 'account_id')
cp_password = cp.get('default', 'password')


#基于Boll指标的开仓、设止损、平保、错单处理函数
#----------------------Api 主函数----------------------
def order_by_boll(symbol, gsd, gpd, period, margin, trade_count):
    # 创建API实例
    api = TqApi(TqAccount(cp_broker_id, cp_account_id, cp_password),web_gui=False)
    # 获取交易品种信息、账户信息
    quote = api.get_quote(symbol)
    account = api.get_account()
    position = api.get_position(symbol)
    #print(position)
    target_pos = TargetPosTask(api, symbol)
    #获取K线信息
    klines = api.get_kline_serial(symbol,period * 60)  #24 * 60 * 60 日线，5*60，5分钟K线
    #计算boll的值
    boll = BOLL(klines, 26, 12)
    #在web_gui中画出mid的值
    klines["boll_stp"] = boll["mid"]
    klines["boll_stp.color"] = 0xff0000ff  #绿色：0xFF00FF00，蓝色：0xff0000ff
    #---自定义变量---
    #循环变量
    loop = 0
    #持仓止损阶段，分为三个阶段，①，初次建仓固定点数止损，如：10点，②当涨跌N点后，设置平仓保护，③当持有多单（空单）且均线值在三个止损值种最大（小）时以均线止损
    g_flag_long = 0
    g_flag_short = 0
    #初次止损价差，即：第一级止损位和开仓价的价差
    g_stp_diff = gsd
    #止损价
    g_stp_long = 0
    g_stp_short = 0
    #平仓保护价差
    g_pb_diff = gpd
    #多空持仓量前值
    g_pre_pos_long = 0
    g_pre_pos_short = 0
    #允许开仓几次
    g_trade_count = 0
    #下单量
    g_lots=0
    #多空开关
    g_long_enabled=True #True允许做多，False不允许做多
    g_short_enabled=True
    #建立个函数，初始化全局变量
    def g_init():
        g_flag_long = 0 # 用来在持有多单时，控制初始止损、平保、移动止损，在没有多单、多单平仓之后，该变量应该初始化为0
        g_flag_short = 0
        g_pre_pos_long = 0 # 用来记录前一持仓的持仓量
        g_pre_pos_short = 0

    while True:
        # 调用 wait_update 等待业务信息发生变化，例如: 行情发生变化, 委托单状态变化, 发生成交等等
        # 注意：其他合约的行情的更新也会触发业务信息变化，因此这里可能会将同一笔行情输出多次
        #异常处理
        try:
            api.wait_update()
            #移动平均线前值，即止损值
            pre_boll_value = round(boll["mid"].iloc[-2], 2)
            #计算开仓量，注意计算公式可根据自己需要调整
            g_lots=math.floor((account.available*0.2/margin)*0.8)
            #净持仓发生变化时，输出账户相关信息,该信息可自行修改
            if api.is_changing(position, ["pos_long_his", "pos_long_today", "pos_short_his","pos_short_today"]):
                log_time = "轮询次数：", loop, "时间：", quote.datetime
                log_info=symbol+"  最新价：",quote.last_price,"账户权益：",round(account.balance),"多单持仓量：",position.pos_long,\
                    "多单持仓均价：",position.position_price_long,"空单持仓量：",position.pos_short,"空单持仓均价：",\
                    position.position_price_short,"止损均线值：",pre_boll_value
                #print(log_time,"\n",log_info,"\n")
                #将账户信息存入日志
                if not os.path.exists(os.getcwd() + "\\logs"):
                    os.mkdir(os.getcwd() + "\\logs")
                f_name = time.strftime("%Y-%m-%d", time.localtime())
                f = open(os.getcwd() + "\\logs\\" + f_name + ".txt","a",encoding="utf-8")
                f.write(str(log_time))
                f.write(str(log_info) + "\n")
                f.close()
                #循环变量自加
                loop = loop + 1
                #持仓发生变化，则交易次数加1
                g_trade_count += 1

            #根据行情发生变化，建仓及设置动态止损和设平仓保护
            if api.is_changing(quote,["last_price", "bid_volume1", "ask_volume1"]):
                #止损均线下方不允许做多，上方不允许做空
                #多头平仓
                if position.pos_long>0 and klines.close.iloc[-2] < round(boll["mid"].iloc[-2],2) and klines.close.iloc[-3] < round(boll["mid"].iloc[-3],2):  #quote.last_price<pre_boll_value :
                    target_pos.set_target_volume(0)
                    #将成交记录录入数据库
                    insert_orders_table(symbol,"BUY","OPEN",position.pos_long,quote.last_price,quote.datetime,position.position_profit_long,"多头错单")
                    print(symbol+"  多头错单")
                    g_init()

                #空头平仓
                if position.pos_short>0 and klines.close.iloc[-2] > round(boll["mid"].iloc[-2],2) and klines.close.iloc[-3] > round(boll["mid"].iloc[-3],2):  #and quote.last_price>pre_boll_value:
                    target_pos.set_target_volume(0)
                    #将成交记录录入数据库
                    insert_orders_table(symbol,"BUY","OPEN",position.pos_short,quote.last_price,quote.datetime,position.position_profit_short,"空头错单")
                    print(symbol+"  空头错单")
                    g_init()

                #空仓时候，初始化g_flag
                if position.pos_long == 0 and position.pos_short == 0 and (g_flag_long!=0 or g_flag_short!=0 or g_pre_pos_long!=0 or g_pre_pos_short!=0):
                    g_init()
                    #print("账户无持仓，初始化全局变量")

            #根据行情发生变化，建仓及设置动态止损和设平仓保护
            if api.is_changing(quote,["last_price", "bid_volume1", "ask_volume1"]):
                #print("昨日收盘价：",klines.close.iloc[-1],"昨日 MID 值：", round(boll["mid"].iloc[-1],2) ,"前日收盘价：",klines.close.iloc[-2],"前日Mid值：", round(boll["mid"].iloc[-2],2))
                #如果空仓，那么，价格金叉boll中轨做多，价格死叉boll做空
                if True: #g_trade_count <= trade_count
                    #如果金叉
                    if g_long_enabled and position.pos_long < g_lots  and klines.close.iloc[-2] > round(boll["mid"].iloc[-2],2) and klines.close.iloc[-3] < round(boll["mid"].iloc[-3],2):  #postion.pos_long<lots and
                        target_pos.set_target_volume(g_lots)
                        #如果已经有空单，把空单平仓信息录入数据库
                        if position.pos_short>0:
                            insert_orders_table(symbol,"BUY","CLOSE",position.pos_short,quote.last_price,quote.datetime,position.position_profit_short,"空单平仓准备开多")
                            insert_orders_table(symbol,"BUY","OPEN",g_lots,quote.last_price,quote.datetime,"nan","空头平仓后多头开仓")
                        else:
                            #将成交记录录入数据库
                            insert_orders_table(symbol,"BUY","OPEN",g_lots,quote.last_price,quote.datetime,"nan","多头开仓")
                        #初始化止损控制变量
                        g_init()
                        print(symbol + "  金叉做多", g_lots)
                    #如果死叉
                    if g_short_enabled and position.pos_short < g_lots and klines.close.iloc[-2] < round(boll["mid"].iloc[-2],2) and klines.close.iloc[-3] > round(boll["mid"].iloc[-3],2):  #postion.pos_short<lots and
                        target_pos.set_target_volume(-g_lots)
                        if position.pos_long>0:
                            insert_orders_table(symbol,"SELL","CLOSE",position.pos_long,quote.last_price,quote.datetime,position.position_profit_long,"多单平仓准备开空")
                            insert_orders_table(symbol,"SELL","OPEN",-g_lots,quote.last_price,quote.datetime,"nan","多单平仓后空头开仓")
                        else:
                            #将成交记录录入数据库
                            insert_orders_table(symbol,"SELL","OPEN",-g_lots,quote.last_price,quote.datetime,"nan","空头开仓")
                        #初始化止损控制变量
                        g_init()
                        print(symbol + "  死叉做空", -g_lots)
                    
                #多单设止损及平保
                if position.pos_long > 0 and position.open_price_long != None:
                    if g_flag_long < 1:
                        g_stp_long = round(position.open_price_long,2) - g_stp_diff
                    if (g_flag_long < 2 and quote.last_price >round(position.open_price_long, 2) + g_stp_diff) or g_flag_long == 1:
                        g_stp_long = round(position.open_price_long,2) + g_pb_diff
                        g_flag_long = 1
                    if (g_flag_long < 3 and pre_boll_value >round(position.open_price_long, 2) + g_stp_diff) or g_flag_long == 2:
                        g_stp_long = pre_boll_value
                        g_flag_long = 2

                    if position.pos_long != g_pre_pos_long:
                        g_pre_pos_long = position.pos_long
                        print("代码", symbol, "时间：", quote.datetime, "多单持仓量：",round(position.pos_long, 2), "多单持仓均价：",round(position.open_price_long, 2), "多单初损值：",round(g_stp_long, 2), "多单当前止损值：",round(g_stp_long, 2)) #达到止损条件则平多单
                    if quote.last_price < g_stp_long:
                        target_pos.set_target_volume(0)
                        #将成交记录录入数据库
                        insert_orders_table(symbol,"SELL","CLOSE",position.pos_long,quote.last_price,quote.datetime,position.position_profit_long,"多单止损")
                        g_init()
                        print(symbol + "  止损平多单","  pos:",position.pos_long," ",g_stp_long)

                #空单设止损及平保
                if position.pos_short > 0 and position.open_price_short != None:
                    if g_flag_short < 1:
                        g_stp_short = round(position.open_price_short,2) + g_stp_diff
                    if (g_flag_short < 2 and quote.last_price <round(position.open_price_short, 2) - g_stp_diff) or g_flag_short == 1:
                        g_stp_short = round(position.open_price_short,2) - g_pb_diff
                        g_flag_short = 1
                    if (g_flag_short < 3 and pre_boll_value <round(position.open_price_short, 2) - g_stp_diff) or g_flag_short == 2:
                        g_stp_short = pre_boll_value
                        g_flag_short = 2

                    if position.pos_short != g_pre_pos_short:
                        g_pre_pos_short = position.pos_short
                        print("代码", symbol, "时间：", quote.datetime, "空单持仓量：",round(position.pos_short, 2), "空单持仓均价：",round(position.open_price_short, 2), "空单初损值：",round(g_stp_short, 2), "空单当前止损值",round(g_stp_short, 2)) #达到止损条件则平空单
                    if quote.last_price > g_stp_short:
                        target_pos.set_target_volume(0)
                        #将成交记录录入数据库
                        insert_orders_table(symbol,"BUY","CLOSE",position.pos_short,quote.last_price,quote.datetime,position.position_profit_short,"空单止损")
                        g_init()
                        print(symbol + "  止损平空单","  pos:",position.pos_short)
                #主图上画出止损线
                klines["boll_stp"] = boll["mid"]
        except Exception as e:
            raise e
            #pass

    #关闭实例，释放资源
    api.close()


#----------------------其他需要用到的函数----------------------


#发送Email给指定邮箱，监控程序运行状态
def send_mail_accinfo():
    # 创建API实例
    acc_info = TqApi(TqAccount(cp_broker_id, cp_account_id,cp_password))  #登录上期模拟
    # 获取账户信息
    account = acc_info.get_account()
    msg = u"今日账户  浮动盈亏：" + str(account.float_profit)+"  平仓盈亏："+str(account.close_profit)
    send_mail(msg)
    #关闭api
    acc_info.close()


#终端输出账户信息，用线程调用，每1分钟输出一次
def print_accinfo():
    # 创建API实例
    acc_info = TqApi(TqAccount(cp_broker_id, cp_account_id,cp_password))  #登录上期模拟
    # 获取账户信息
    account = acc_info.get_account()
    msg = u"*** 今日账户信息 ***  持仓盈亏：" + str(account.float_profit)+"  平仓盈亏："+str(account.close_profit)
    print(msg)
    #关闭api
    acc_info.close()

#将成交记录，录入数据库
def insert_orders_table(symbol,direction,offset,lots,price,dt,profit,note):
    #print("找到数据库录入函数")
    try:
        list=[symbol,direction,offset,lots,price,dt,profit,note]
        str_list=[str(x) for x in list]
        #print(str_list)
        conn = sqlite3.connect(r"data.db")
        print("conn",conn)
        c = conn.cursor()
        #e.execute('''INSERT INTO orders(id,symbol,direction,offset,lots,price,dt,profit,note) VALUES(NULL,?,?,?,?,?,?,?,?)''',str_list)
        sql="INSERT INTO orders VALUES(NULL,?,?,?,?,?,?,?,?)"
        c.execute(sql,str_list)
        conn.commit()
        c.close()
        conn.close()
        print(symbol+"  成交记录，已录入数据库！")
    except Exception as e:
        #raise e
        pass


#调度函数，定时发送邮件
def apscheduler_send_mail():
    scheduler = BackgroundScheduler()
    scheduler.add_job(send_mail_accinfo, 'cron', hour=8, minute=55)
    scheduler.add_job(send_mail_accinfo, 'cron', hour=15, minute=5)
    scheduler.add_job(send_mail_accinfo, 'cron', hour=20, minute=55)
    scheduler.add_job(send_mail_accinfo, 'cron', hour=23, minute=5)
    #测试
    scheduler.add_job(send_mail_accinfo, 'cron', hour=23, minute=45)
    scheduler.start()
